package rsi.mesoscopic.view;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.geom.AffineTransform;
import java.awt.geom.GeneralPath;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.Map;

import javax.swing.ImageIcon;

import rsi.mesoscopic.math.Geom;
import rsi.mesoscopic.model.NetworkModel;
import rsi.mesoscopic.model.NetworkModel.CarSource;
import rsi.mesoscopic.model.Node;

public class NetworkView implements View {

	private static final String GREEN_SIGNAL_LEGEND_TEXT = "Moviment coming from the nearest street is not allowed.";

	private static final String RED_SIGNAL_LEGEND_TEXT = "Moviment coming from the nearest street is allowed.";

	private static final String TRAFIC_DIRECTION_LEGEND_TEXT = "Traffic flow direction.";

	private static final String CAR_QUEUE_LEGEND_TEXT = "Car on a queue.";

	private static final String CAR_ON_SATURED_LEGEND_TEXT = "Car on satured queue.";

	private static final String CAR_GENERATED_LEGEND_TEXT = "Number of cars inserted on simulation on the nearest node.";

	private static final int STREET_WIDTH = 30;

	private static final int STREET_WIDTH_RATIO = STREET_WIDTH/2;

	private static final int JUNCTION_BORDER = 0;

	private static final int CAR_SIZE = 4;

	private static final int CAR_GAP = 2;

	private static final int STRIP_WIDTH = 5;

	private static final Color COLOR_SIGNAL_GREEN = Color.green;

	private static final Color COLOR_SIGNAL_RED = Color.red;

	private static final Color COLOR_SIGNAL_YELLOW = Color.yellow;

	private static final Color COLOR_STREET = new Color(0x888888);

	private static final Color COLOR_STREET_BORDER = Color.black;

	private static final Color COLOR_ARROW = Color.lightGray;

	private static final Color COLOR_CAR_ON_QUEUE = Color.yellow;

	private static final Color COLOR_CAR_ON_HUGE_QUEUE = Color.red;

	private static final Color COLOR_SOURCE_INDICATOR = Color.BLUE;

	private static final Font NEW_CARS_FONT = new Font("Arial", Font.BOLD, 14);

	private static final Font LEGEND_FONT = new Font("Arial", Font.PLAIN, 10);

	private final Image mCompassRose;

	private final NetworkModel mModel;

	public NetworkView(NetworkModel m) {
		mModel = m;

		mCompassRose = new ImageIcon(getClass().getResource("/rsi/mesoscopic/images/compass_rose.jpg")).getImage();
	}

	@Override
	public void draw(Canvas c) {

		Graphics2D g = c.getGraphics();

		Rectangle2D.Double contentSquare = getNetworkDimension();
		Rectangle clipBounds = g.getClipBounds();
		double xCenter = (clipBounds.width - contentSquare.width)/2;
		double yCenter = (clipBounds.height - contentSquare.height)/2;

		drawCompassRose(g);

		AffineTransform originalTransform = g.getTransform();
		AffineTransform legendTransform = new AffineTransform();
		Rectangle2D legendRec = calcLegendSize(g);
		legendTransform.translate(clipBounds.width - legendRec.getWidth() , clipBounds.height - legendRec.getHeight());
		g.setTransform(legendTransform);
		drawLegends(g);
		g.setTransform(originalTransform);

	    originalTransform = g.getTransform();
	    //Ajusta ao tamanho da tela
        Toolkit toolkit =  Toolkit.getDefaultToolkit ();
        Dimension dim = toolkit.getScreenSize();
        dim.height -= 50;
        dim.width -= 50;

        AffineTransform af = new AffineTransform();
        af.translate(contentSquare.x*-1, contentSquare.y*-1);
        af.scale(0.5, 0.5);
        g.transform(af);

		//Centraliza
		AffineTransform centerTransform = new AffineTransform();
		centerTransform.translate(xCenter, yCenter);
		g.transform(centerTransform);

		//Draw node junction
		for(int i = 0; i < mModel.nodeCount(); i++) {
			drawJunction(mModel.getNode(i), g);
		}

		//Draw streets
		Node node = null;
		for(int i = 0; i < mModel.nodeCount(); i++) {
			node = mModel.getNode(i);
			for(Node neigbor : node.getOutcomingNodes()) {
				drawStreet(node, neigbor, g);
			}
		}

		//Draw node data
		for(int i = 0; i < mModel.nodeCount(); i++) {
			drawNodeData(mModel.getNode(i), g);
		}

		drawSources(g);

		g.setTransform(originalTransform);
	}

	private Rectangle2D.Double getNetworkDimension() {
		Rectangle2D.Double rect = new Rectangle2D.Double();
		rect.x = 999999;
		rect.y = 999999;
		double maxX = -99999, maxY = -99999;

		for(int i = 0; i < mModel.nodeCount(); i++) {
			Node n = mModel.getNode(i);

			if(n.getX() < rect.x) {
				rect.x = n.getX();
			}

			if(n.getX() > maxX) {
				maxX = n.getX();
			}

			if(n.getY() < rect.y) {
				rect.y = n.getY();
			}

			if(n.getY() > maxY) {
				maxY = n.getY();
			}
		}

		rect.width = maxX - rect.x;
		rect.height = maxY - rect.y;

		return rect;
	}

	private void drawJunction(Node n, Graphics2D g) {

		Rectangle2D.Double rect = new Rectangle2D.Double();

		double r = (JUNCTION_BORDER + STREET_WIDTH)/2;

		rect.x = n.getX()-r;
		rect.y = n.getY()-r;
		rect.width = rect.height = JUNCTION_BORDER + STREET_WIDTH;

		g.setColor(COLOR_STREET);
		g.fill(rect);

		g.setColor(COLOR_STREET_BORDER);
		g.draw(rect);

//		g.setColor(Color.green);
//		g.fillOval(n.getX()-2, n.getY()-2, 4, 4);
	}

	private void drawStreet(Node n1, Node n2, Graphics2D g2) {

		double coefAng = Geom.getCoefAngular(n1.getX(), n1.getY(), n2.getX(), n2.getY());

		Point2D.Double n1P1 = new Point2D.Double();
		Point2D.Double n1P2 = new Point2D.Double();

		double ccwAngle = Math.atan(coefAng)+Math.PI/2;
		double cwAngle  = Math.atan(coefAng)-Math.PI/2;

		n1P1.x = n1.getX() + Math.cos(ccwAngle)*STREET_WIDTH_RATIO;
		n1P1.y = n1.getY() + Math.sin(ccwAngle)*STREET_WIDTH_RATIO;

		n1P2.x = n1.getX() + Math.cos(cwAngle)*STREET_WIDTH_RATIO;
		n1P2.y = n1.getY() + Math.sin(cwAngle)*STREET_WIDTH_RATIO;

		Point2D.Double n2P1 = new Point2D.Double();
		Point2D.Double n2P2 = new Point2D.Double();

		n2P1.x = n2.getX() + Math.cos(ccwAngle)*STREET_WIDTH_RATIO;
		n2P1.y = n2.getY() + Math.sin(ccwAngle)*STREET_WIDTH_RATIO;

		n2P2.x = n2.getX() + Math.cos(cwAngle)*STREET_WIDTH_RATIO;
		n2P2.y = n2.getY() + Math.sin(cwAngle)*STREET_WIDTH_RATIO;

		double junctionWidth = STREET_WIDTH + JUNCTION_BORDER;

		getPointOnRet(n1P1, n2P1, new Rectangle2D.Double(n1.getX()-STREET_WIDTH_RATIO, n1.getY()-STREET_WIDTH_RATIO, junctionWidth, junctionWidth));
		getPointOnRet(n1P2, n2P2, new Rectangle2D.Double(n1.getX()-STREET_WIDTH_RATIO, n1.getY()-STREET_WIDTH_RATIO, junctionWidth, junctionWidth));

		getPointOnRet(n2P1, n1P1, new Rectangle2D.Double(n2.getX()-STREET_WIDTH_RATIO, n2.getY()-STREET_WIDTH_RATIO, junctionWidth, junctionWidth));
		getPointOnRet(n2P2, n1P2, new Rectangle2D.Double(n2.getX()-STREET_WIDTH_RATIO, n2.getY()-STREET_WIDTH_RATIO, junctionWidth, junctionWidth));

		GeneralPath gp = new GeneralPath();

		gp.moveTo(n1P1.x, n1P1.y);
		gp.lineTo(n1P2.x, n1P2.y);
		gp.lineTo(n2P2.x, n2P2.y);
		gp.lineTo(n2P1.x, n2P1.y);

		gp.closePath();

		g2.setColor(COLOR_STREET);
		g2.fill(gp);

		g2.setColor(COLOR_STREET_BORDER);
		g2.draw(new Line2D.Double(n1P1.getX(), n1P1.getY(), n2P1.getX(), n2P1.getY()));
		g2.draw(new Line2D.Double(n1P2.getX(), n1P2.getY(), n2P2.getX(), n2P2.getY()));

		//Draw arrow
		AffineTransform saveAt = g2.getTransform();

		AffineTransform arrowAt = new AffineTransform();
		double x1 = n1.getX();
		double x2 = n2.getX();
		double y1 = n1.getY();
		double y2 = n2.getY();
		double arrowX = (3*x1 + x2)/4;
		double arrowY = (3*y1 + y2)/4;
		double arrowSize = Math.sqrt(Math.pow(x2-x1, 2) + Math.pow(y2-y1,2))/2;
		double teta = Math.atan(coefAng) + (x2 < x1 ? Math.PI : 0);

		arrowAt.rotate(teta, arrowX, arrowY);
		g2.transform(arrowAt);
		g2.setColor(COLOR_ARROW);
		g2.draw(new Line2D.Double(arrowX, arrowY, arrowX+arrowSize, arrowY));

		GeneralPath arrowHead = new GeneralPath();
		double arrowHX = arrowX+arrowSize;
		double arrowHY = arrowY;
		arrowHead.moveTo(arrowHX, arrowHY);
		arrowHead.lineTo(arrowHX-5, arrowHY-5);
		arrowHead.lineTo(arrowHX-5, arrowHY+5);
		arrowHead.closePath();
		g2.fill(arrowHead);

		g2.setTransform(saveAt);

		//Debug
//		g2.setColor(Color.red);
//		g2.fillOval((int)n1P1.x-1, (int)n1P1.y-1, 2, 2);
//		g2.fillOval((int)n1P2.x-1, (int)n1P2.y-1, 2, 2);
//		g2.fillOval((int)n2P1.x-1, (int)n2P1.y-1, 2, 2);
//		g2.fillOval((int)n2P2.x-1, (int)n2P2.y-1, 2, 2);

	}

	private void drawNodeData(Node node, Graphics2D g2) {
		//Draw cars
		//Get car numbers

		int incomingIndex = 0;
		int queueSize = 0;

		double carQueueX = 0;
		double carQueueY = 0;
		double teta = 0;
		int maxCars = 0;
		int numCarsLines;
		int numCarsColumn = STREET_WIDTH/(2*CAR_GAP + CAR_SIZE);

		AffineTransform saveAt;

		for(Node incoming : node.getIncomingNodes()) {
			queueSize = node.getQueue(incomingIndex);

			carQueueX = node.getX()-STREET_WIDTH_RATIO + (node.getX() < incoming.getX() || node.getY() > incoming.getY() ? STREET_WIDTH : 0);
			carQueueY = node.getY()-STREET_WIDTH_RATIO + (node.getY() < incoming.getY() ? STREET_WIDTH: 0);

			numCarsLines = (int)Geom.getDistance(node.getX(), node.getY(), incoming.getX(), incoming.getY())/(2*CAR_GAP + CAR_SIZE);
			maxCars = numCarsLines * numCarsColumn;

			teta = Geom.getAngle(incoming.getX(), incoming.getY(), node.getX(), node.getY());

			incomingIndex++;

			//Start draw
			saveAt = g2.getTransform();
			AffineTransform carsTransform = new AffineTransform();
			carsTransform.translate(carQueueX, carQueueY);
			if(node.getX() < incoming.getX()) {
				carsTransform.translate(0, STREET_WIDTH);
			}
			carsTransform.rotate(teta);
			g2.transform(carsTransform);
			drawQueue(queueSize, maxCars, g2);

			char trafficLightStatus = node.getTrafficLightStatus(incoming.getId());
			drawTrafficLight(g2, trafficLightStatus);

			g2.setTransform(saveAt);

			g2.setColor(Color.WHITE);
		}
	}

	private void getPointOnRet(Point2D.Double original, Point2D.Double against, Rectangle2D.Double rect) {

		//Se o ponto de referencia estiver abaixo
		if(original.y < against.y) {

			double diffBaseY= Math.abs(original.y-(rect.getY()+rect.getHeight()));

			if(original.x < against.x) {
				//Se o ponto referencia estiver a direita e abaixo

				double diffDirX = Math.abs(original.x-(rect.getX()+rect.getWidth()));

				if(diffDirX < diffBaseY) {
					original.x = rect.getX() + rect.getWidth();
				} else {
					original.y = rect.getY()+rect.getHeight();
				}

			} else {
				//Se o ponto referencia estiver a esquerda e abaixo

				double diffEsqX = Math.abs(original.x-rect.getX());

				if(diffEsqX == 0) {
					original.y = rect.getY()+rect.getHeight()-2;
				}else if(diffEsqX < diffBaseY) {
					original.x = rect.getX();
				} else {
					original.y = rect.getY()+rect.getHeight();
				}

			}

		} else if(original.y > against.y) {
			double diffTopoY = Math.abs(original.y-rect.getY());

			if(original.x < against.x) {
				//Se o ponto de referencia estiver acima e a direita
				double diffDirX  = Math.abs(original.x-(rect.getX()+rect.getWidth()));

				if(diffDirX < diffTopoY) {
					original.x = rect.getX() + rect.getWidth();
				}else {
					original.y = rect.getY();
				}


			} else {
				//Se o ponto de referencia estiver acima e a esquerda
				double diffEsqX = Math.abs(original.x-rect.getX());

				if(diffEsqX == 0) {
					original.y = rect.getY()+2;
				}else if(diffEsqX < diffTopoY) {
					original.x = rect.getX();
				}else {
					original.y = rect.getY();
				}

			}
		} else {
			if(original.x < against.x) {
				original.x = rect.getX() + rect.getWidth();
			} else {
				original.x = rect.getX()+2;
			}
		}

	}

	private void drawTrafficLight(Graphics2D g, char status) {

		if(status == '-'){
			return;
		}

		Rectangle2D.Double strip = new Rectangle2D.Double();
		strip.x = -6;
		strip.y =  2;
		strip.width = 5;
		strip.height = STREET_WIDTH - 4;

		switch(status) {
		case 'r':
			g.setColor(COLOR_SIGNAL_RED);
			break;
		case 'g':
			g.setColor(COLOR_SIGNAL_GREEN);
			break;
		case 'y':
			g.setColor(COLOR_SIGNAL_YELLOW);
			break;

		}
		g.fill(strip);
	}

	private void drawQueue(int queueSize, int queueMaxSize, Graphics2D g) {

		int linha = 0;
		int coluna = 0;

		int numMaxLinhas = STREET_WIDTH/(CAR_SIZE+2*CAR_GAP);

		Rectangle2D.Double car = new Rectangle2D.Double();
		car.width = car.height = CAR_SIZE;

		int numberOfCarsToDraw = Math.min(queueMaxSize, queueSize);

		g.setColor(queueSize > queueMaxSize ? COLOR_CAR_ON_HUGE_QUEUE : COLOR_CAR_ON_QUEUE);
		for(int i = 0; i < numberOfCarsToDraw; i++) {

			car.x = -1*coluna * (CAR_SIZE + 2*CAR_GAP) - CAR_SIZE - CAR_GAP - STRIP_WIDTH;
			car.y = linha * (CAR_SIZE + 2*CAR_GAP) + CAR_GAP;

			g.fill(car);

			linha++;
			if(linha > numMaxLinhas) {
				linha = 0;
				coluna++;
			}
		}
	}

	private void drawSources(Graphics2D g2) {
		int curStep = mModel.getCurrentStep();
		int curAmount = 0;
		Node curNode = null;

		g2.setColor(COLOR_SOURCE_INDICATOR);
		g2.setFont(NEW_CARS_FONT);

		for(Map.Entry<Node, CarSource> entry : mModel.getSource().entrySet()) {
			curAmount = entry.getValue().amount.get(curStep);
			if(curAmount == 0) continue;

			curNode = entry.getKey();

			g2.drawString("+" + curAmount,
					curNode.getX() - STREET_WIDTH, curNode.getY() - STREET_WIDTH);
		}
	}

	private void drawCompassRose(Graphics2D g) {
		Rectangle clipBounds = g.getClipBounds();

		double x = clipBounds.getX();
		double y = clipBounds.getY() + clipBounds.getHeight() - 60;

		g.drawImage(mCompassRose, (int)x, (int)y, null);
	}

	private void drawLegends(Graphics2D g) {

		Rectangle2D.Double redSignal = new Rectangle2D.Double();
		Rectangle2D.Double greenSignal = new Rectangle2D.Double();

		int legendHeight = 15;
		int legendWidth = 25;

		redSignal.x = 0;
		redSignal.y = 0;
		redSignal.height = STREET_WIDTH/2;
		redSignal.width = STRIP_WIDTH*3;

		greenSignal.x = 0;
		greenSignal.y = redSignal.y + legendHeight + 5;
		greenSignal.height = STREET_WIDTH/2;
		greenSignal.width = STRIP_WIDTH*3;

		GeneralPath arrow = new GeneralPath();
		Point2D.Double arrowStart = new Point2D.Double();
		Point2D.Double arrowEnd = new Point2D.Double();
		arrowStart.x = 0;
		arrowStart.y = greenSignal.y + legendHeight + 5 + 7;
		arrowEnd.x = STRIP_WIDTH*3;
		arrowEnd.y = arrowStart.y;

		arrow.moveTo(arrowEnd.x, arrowEnd.y);
		arrow.lineTo(arrowEnd.x - 5, arrowEnd.y - 5);
		arrow.lineTo(arrowEnd.x - 5, arrowEnd.y + 5);
		arrow.closePath();

		Rectangle2D.Double carOnQueue = new Rectangle2D.Double();
		carOnQueue.x = 0;
		carOnQueue.y = arrowStart.y + legendHeight;
		carOnQueue.width = CAR_SIZE;
		carOnQueue.height = CAR_SIZE;

		Rectangle2D.Double carOnHugeQueue = new Rectangle2D.Double();
		carOnHugeQueue.x = 0;
		carOnHugeQueue.y = carOnQueue.y + legendHeight + 4;
		carOnHugeQueue.width = CAR_SIZE;
		carOnHugeQueue.height = CAR_SIZE;

		Point2D.Double sourceIndicator = new Point2D.Double();
		sourceIndicator.x = 0;
		sourceIndicator.y = carOnHugeQueue.y + legendHeight + 10;

		g.setColor(COLOR_SIGNAL_RED);
		g.fill(redSignal);

		g.setColor(COLOR_SIGNAL_GREEN);
		g.fill(greenSignal);

		g.setColor(Color.BLACK);
		g.fill(arrow);
		g.drawLine((int)arrowStart.x, (int)arrowStart.y, (int)arrowEnd.x - 4, (int)arrowEnd.y);

		g.setColor(COLOR_CAR_ON_QUEUE);
		g.fill(carOnQueue);

		g.setColor(COLOR_CAR_ON_HUGE_QUEUE);
		g.fill(carOnHugeQueue);

		g.setColor(COLOR_SOURCE_INDICATOR);
		g.setFont(NEW_CARS_FONT);
		g.drawString("+1", (int)sourceIndicator.x, (int)sourceIndicator.y);

		g.setColor(Color.BLACK);
		g.setFont(LEGEND_FONT);

		g.drawString(RED_SIGNAL_LEGEND_TEXT, legendWidth,
				(int)(redSignal.y + redSignal.getHeight()/2 + 4));

		g.drawString(GREEN_SIGNAL_LEGEND_TEXT, legendWidth,
				(int)(greenSignal.y + greenSignal.getHeight()/2 + 4));

		g.drawString(TRAFIC_DIRECTION_LEGEND_TEXT, legendWidth,
				(int)(arrowStart.y + 4));

		g.drawString(CAR_QUEUE_LEGEND_TEXT, legendWidth,
				(int)(carOnQueue.y + 7));

		g.drawString(CAR_ON_SATURED_LEGEND_TEXT, legendWidth,
				(int)(carOnHugeQueue.y + 7));

		g.drawString(CAR_GENERATED_LEGEND_TEXT, legendWidth,
				(int)(sourceIndicator.y ));
	}

	private Rectangle2D.Double calcLegendSize(Graphics2D g) {

		Rectangle2D.Double legend = new Rectangle2D.Double();

		legend.width = 320;

		legend.height = 120;

		return legend;
	}
}
